package com.bowlong.bio3;

// boolean
// byte[]
// byte
// Date
// double
// int[]
// int
// List
// long
//Map
// null
// Set
// String
// String[]
import static com.bowlong.bio3.B3Types.*;
import static com.bowlong.bio3.B3Constants.*;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.Set;

@SuppressWarnings("unchecked")
public class B3OutputStream implements B3Output {

	int MAX_SIZE = 4 * 1024 * 1024;
	int ptr;
	final OutputStream out;

	public B3OutputStream(OutputStream out) {
		this.out = out;
	}

	public B3OutputStream(OutputStream out, int MAX_SIZE) {
		this.out = out;
		this.MAX_SIZE = MAX_SIZE;
	}

	@Override
	public void write(int b) throws IOException {
		write(out, b);
		ptr++;

		if (ptr() > MAX_SIZE)
			throw new IOException("MAX_SIZE:" + MAX_SIZE);
	}

	public static void write(OutputStream out, int b) throws IOException {
		out.write(b);
	}

	@Override
	public void write(byte[] b) throws IOException {
		write(out, b);
	}

	public static void write(OutputStream out, byte[] b) throws IOException {
		write(out, b, 0, b.length);
	}

	@Override
	public void write(byte[] b, int off, int len) throws IOException {
		write(out, b, off, len);
		ptr += len;
		if (ptr() > MAX_SIZE)
			throw new IOException("MAX_SIZE:" + MAX_SIZE);
	}

	public static void write(OutputStream out, byte[] b, int off, int len)
			throws IOException {
		out.write(b, off, len);
	}

	// ////////////////
	public int ptr() {
		return ptr;
	}

	// ////////////////
	public void write(B3Types type) throws IOException {
		write(out, type);
	}

	public static void write(OutputStream out, B3Types type) throws IOException {
		int tag = type.ordinal();
		write(out, tag);
	}

	@Override
	public void writeBoolean(boolean v) throws IOException {
		writeBoolean(out, v);
	}

	public void writeBoolean(AtomicBoolean v) throws IOException {
		writeBoolean(out, v);
	}

	public static void writeBoolean(OutputStream out, AtomicBoolean v)
			throws IOException {
		writeBoolean(out, v.get());
	}

	public static void writeBoolean(OutputStream out, boolean v)
			throws IOException {
		if (v)
			write(out, BOOL_TRUE);
		else
			write(out, BOOL_FALSE);
	}

	@Override
	public void writeByte(int v) throws IOException {
		writeByte(out, v);
	}

	public static void writeByte(OutputStream out, int v) throws IOException {
		if (v == 0) {
			write(out, BYTE_0);
		} else {
			write(out, BYTE);
			write(out, v);
		}
	}

	@Override
	public void writeShort(int v) throws IOException {
		writeShort(out, v);
	}

	public static void writeShort(OutputStream out, int v) throws IOException {
		writeInt(out, v);
	}

	@Override
	public void writeChar(int v) throws IOException {
		throw new NoSuchMethodError();
	}

	@Override
	public void writeInt(int v) throws IOException {
		writeInt(out, v);
	}

	public void writeInt(AtomicInteger v) throws IOException {
		writeInt(out, v);
	}

	public static void writeInt(OutputStream out, AtomicInteger v)
			throws IOException {
		writeInt(out, v.get());
	}

	public static void writeInt(OutputStream out, int v) throws IOException {
		switch (v) {
		case -1:
			write(out, INT_N1);
			break;
		case 0:
			write(out, INT_0);
			break;
		case 1:
			write(out, INT_1);
			break;
		case 2:
			write(out, INT_2);
			break;
		case 3:
			write(out, INT_3);
			break;
		case 4:
			write(out, INT_4);
			break;
		case 5:
			write(out, INT_5);
			break;
		case 6:
			write(out, INT_6);
			break;
		case 7:
			write(out, INT_7);
			break;
		case 8:
			write(out, INT_8);
			break;
		case 9:
			write(out, INT_9);
			break;
		case 10:
			write(out, INT_10);
			break;
		case 11:
			write(out, INT_11);
			break;
		case 12:
			write(out, INT_12);
			break;
		case 13:
			write(out, INT_13);
			break;
		case 14:
			write(out, INT_14);
			break;
		case 15:
			write(out, INT_15);
			break;
		case 16:
			write(out, INT_16);
			break;
		case 17:
			write(out, INT_17);
			break;
		case 18:
			write(out, INT_18);
			break;
		case 19:
			write(out, INT_19);
			break;
		case 20:
			write(out, INT_20);
			break;
		case 21:
			write(out, INT_21);
			break;
		case 22:
			write(out, INT_22);
			break;
		case 23:
			write(out, INT_23);
			break;
		case 24:
			write(out, INT_24);
			break;
		case 25:
			write(out, INT_25);
			break;
		case 26:
			write(out, INT_26);
			break;
		case 27:
			write(out, INT_27);
			break;
		case 28:
			write(out, INT_28);
			break;
		case 29:
			write(out, INT_29);
			break;
		case 30:
			write(out, INT_30);
			break;
		case 31:
			write(out, INT_31);
			break;
		case 32:
			write(out, INT_32);
			break;
		default:
			if (b8(v)) {
				write(out, INT_8B);
				write(out, v);
			} else if (b16(v)) {
				write(out, INT_16B);
				write(out, (byte) ((v >> 8) & 0xff));
				write(out, (byte) ((v >> 0) & 0xff));
			} else {
				write(out, INT_32B);
				write(out, (byte) ((v >> 24) & 0xff));
				write(out, (byte) ((v >> 16) & 0xff));
				write(out, (byte) ((v >> 8) & 0xff));
				write(out, (byte) ((v >> 0) & 0xff));
			}
			break;
		}
	}

	@Override
	public void writeLong(long v) throws IOException {
		writeLong(out, v);
	}

	public void writeLong(AtomicLong v) throws IOException {
		writeLong(out, v);
	}

	public static void writeLong(OutputStream out, AtomicLong v)
			throws IOException {
		writeLong(out, v.get());
	}

	public static void writeLong(OutputStream out, long v) throws IOException {
		if (v == 0) {
			write(out, LONG_0);
		} else if (b8(v)) {
			write(out, LONG_8B);
			write(out, (int) v);
		} else if (b16(v)) {
			write(out, LONG_16B);
			write(out, (byte) ((v >> 8) & 0xff));
			write(out, (byte) ((v >> 0) & 0xff));
		} else if (b32(v)) {
			write(out, LONG_32B);
			write(out, (byte) ((v >> 24) & 0xff));
			write(out, (byte) ((v >> 16) & 0xff));
			write(out, (byte) ((v >> 8) & 0xff));
			write(out, (byte) ((v >> 0) & 0xff));
		} else {
			write(out, LONG_64B);
			write(out, (byte) ((v >> 56) & 0xff));
			write(out, (byte) ((v >> 48) & 0xff));
			write(out, (byte) ((v >> 40) & 0xff));
			write(out, (byte) ((v >> 32) & 0xff));
			write(out, (byte) ((v >> 24) & 0xff));
			write(out, (byte) ((v >> 16) & 0xff));
			write(out, (byte) ((v >> 8) & 0xff));
			write(out, (byte) ((v >> 0) & 0xff));
		}
	}

	@Override
	public void writeFloat(float v) throws IOException {
		writeDouble(v);
	}

	public static void writeFloat(OutputStream out, float v) throws IOException {
		writeDouble(out, v);
	}

	@Override
	public void writeDouble(double var) throws IOException {
		writeDouble(out, var);
	}

	public static void writeDouble(OutputStream out, double var)
			throws IOException {
		long v = Double.doubleToLongBits(var);
		if (v == 0) {
			write(out, DOUBLE_0);
		} else {
			write(out, DOUBLE_64);
			write(out, (byte) ((v >> 56) & 0xff));
			write(out, (byte) ((v >> 48) & 0xff));
			write(out, (byte) ((v >> 40) & 0xff));
			write(out, (byte) ((v >> 32) & 0xff));
			write(out, (byte) ((v >> 24) & 0xff));
			write(out, (byte) ((v >> 16) & 0xff));
			write(out, (byte) ((v >> 8) & 0xff));
			write(out, (byte) ((v >> 0) & 0xff));
		}
	}

	@Override
	public void writeBytes(String s) throws IOException {
		writeBytes(out, s);
	}

	public static void writeBytes(OutputStream out, String s)
			throws IOException {
		if (s == null) {
			write(out, NULL);
		} else if (s.length() == 0) {
			write(out, BYTES_0);
		} else {
			byte[] b = s.getBytes();
			write(out, BYTES);
			writeInt(out, b.length);
			write(out, b);
		}
	}

	@Override
	public void writeBytes(byte[] b) throws IOException {
		writeBytes(out, b);
	}

	public static void writeBytes(OutputStream out, byte[] b)
			throws IOException {
		if (b == null) {
			write(out, NULL);
		} else if (b.length == 0) {
			write(out, BYTES_0);
		} else {
			write(out, BYTES);
			writeInt(out, b.length);
			write(out, b);
		}
	}

	@Override
	public void writeChars(String s) throws IOException {
		throw new NoSuchMethodError();
	}

	@Override
	public void writeUTF(String s) throws IOException {
		writeUTF(out, s);
	}

	public static void writeUTF(OutputStream out, String s) throws IOException {
		if (s == null) {
			write(out, NULL);
		} else if (s.length() == 0) {
			write(out, UTF8_0);
		} else {
			byte[] buf = s.getBytes(UTF_8);
			int size = buf.length;
			switch (size) {
			case 1:
				write(out, UTF8_1);
				break;
			case 2:
				write(out, UTF8_2);
				break;
			case 3:
				write(out, UTF8_3);
				break;
			case 4:
				write(out, UTF8_4);
				break;
			case 5:
				write(out, UTF8_5);
				break;
			case 6:
				write(out, UTF8_6);
				break;
			case 7:
				write(out, UTF8_7);
				break;
			case 8:
				write(out, UTF8_8);
				break;
			case 9:
				write(out, UTF8_9);
				break;
			case 10:
				write(out, UTF8_10);
				break;
			case 11:
				write(out, UTF8_11);
				break;
			case 12:
				write(out, UTF8_12);
				break;
			case 13:
				write(out, UTF8_13);
				break;
			case 14:
				write(out, UTF8_14);
				break;
			case 15:
				write(out, UTF8_15);
				break;
			case 16:
				write(out, UTF8_16);
				break;
			case 17:
				write(out, UTF8_17);
				break;
			case 18:
				write(out, UTF8_18);
				break;
			case 19:
				write(out, UTF8_19);
				break;
			case 20:
				write(out, UTF8_20);
				break;
			case 21:
				write(out, UTF8_21);
				break;
			case 22:
				write(out, UTF8_22);
				break;
			case 23:
				write(out, UTF8_23);
				break;
			case 24:
				write(out, UTF8_24);
				break;
			case 25:
				write(out, UTF8_25);
				break;
			case 26:
				write(out, UTF8_26);
				break;
			case 27:
				write(out, UTF8_27);
				break;
			case 28:
				write(out, UTF8_28);
				break;
			case 29:
				write(out, UTF8_29);
				break;
			case 30:
				write(out, UTF8_30);
				break;
			case 31:
				write(out, UTF8_31);
				break;
			case 32:
				write(out, UTF8_32);
				break;
			default:
				write(out, UTF8);
				writeInt(out, size);
			}
			write(out, buf);
		}

	}

	@Override
	public void writeDate(Date dat) throws IOException {
		writeDate(out, dat);
	}

	public static void writeDate(OutputStream out, Date dat) throws IOException {
		if (dat == null) {
			write(out, NULL);
		} else {
			long v = dat.getTime();
			write(out, DATE);

			write(out, (byte) ((v >> 56) & 0xff));
			write(out, (byte) ((v >> 48) & 0xff));
			write(out, (byte) ((v >> 40) & 0xff));
			write(out, (byte) ((v >> 32) & 0xff));
			write(out, (byte) ((v >> 24) & 0xff));
			write(out, (byte) ((v >> 16) & 0xff));
			write(out, (byte) ((v >> 8) & 0xff));
			write(out, (byte) ((v >> 0) & 0xff));
		}
	}

	@Override
	public void writeList(List<Object> list) throws IOException {
		writeList(out, list);
	}

	public static void writeList(OutputStream out, List<Object> list)
			throws IOException {
		if (list == null) {
			write(out, NULL);
		} else if (list.size() == 0) {
			write(out, LIST_0);
		} else {
			int size = list.size();
			switch (size) {
			case 1:
				write(out, LIST_1);
				break;
			case 2:
				write(out, LIST_2);
				break;
			case 3:
				write(out, LIST_3);
				break;
			case 4:
				write(out, LIST_4);
				break;
			case 5:
				write(out, LIST_5);
				break;
			case 6:
				write(out, LIST_6);
				break;
			case 7:
				write(out, LIST_7);
				break;
			case 8:
				write(out, LIST_8);
				break;
			case 9:
				write(out, LIST_9);
				break;
			case 10:
				write(out, LIST_10);
				break;
			case 11:
				write(out, LIST_11);
				break;
			case 12:
				write(out, LIST_12);
				break;
			case 13:
				write(out, LIST_13);
				break;
			case 14:
				write(out, LIST_14);
				break;
			case 15:
				write(out, LIST_15);
				break;
			case 16:
				write(out, LIST_16);
				break;
			default:
				write(out, LIST);
				writeInt(out, size);
				break;
			}

			for (Object e : list) {
				writeObject(out, e);
			}
		}
	}

	public static void writeListTag(OutputStream out, int size)
			throws IOException {
		if (size == 0) {
			write(out, LIST_0);
		} else {
			switch (size) {
			case 1:
				write(out, LIST_1);
				break;
			case 2:
				write(out, LIST_2);
				break;
			case 3:
				write(out, LIST_3);
				break;
			case 4:
				write(out, LIST_4);
				break;
			case 5:
				write(out, LIST_5);
				break;
			case 6:
				write(out, LIST_6);
				break;
			case 7:
				write(out, LIST_7);
				break;
			case 8:
				write(out, LIST_8);
				break;
			case 9:
				write(out, LIST_9);
				break;
			case 10:
				write(out, LIST_10);
				break;
			case 11:
				write(out, LIST_11);
				break;
			case 12:
				write(out, LIST_12);
				break;
			case 13:
				write(out, LIST_13);
				break;
			case 14:
				write(out, LIST_14);
				break;
			case 15:
				write(out, LIST_15);
				break;
			case 16:
				write(out, LIST_16);
				break;
			default:
				write(out, LIST);
				writeInt(out, size);
				break;
			}
		}
	}

	@Override
	public void writeSet(Set<Object> set) throws IOException {
		writeSet(out, set);
	}

	public static void writeSet(OutputStream out, Set<Object> set)
			throws IOException {
		if (set == null)
			write(out, NULL);
		else if (set.size() == 0)
			write(out, SET_0);
		else {
			int size = set.size();
			write(out, SET);
			writeInt(out, size);
			for (Object t : set) {
				writeObject(out, t);
			}
		}
	}

	@Override
	public void writeMap(Map<Object, Object> map) throws IOException {
		writeMap(out, map);
	}

	public static void writeMap(OutputStream out, Map<Object, Object> map)
			throws IOException {
		if (map == null) {
			write(out, NULL);
		} else if (map.size() == 0) {
			write(out, MAP_0);
		} else {
			int size = map.size();
			switch (size) {
			case 1:
				write(out, MAP_1);
				break;
			case 2:
				write(out, MAP_2);
				break;
			case 3:
				write(out, MAP_3);
				break;
			case 4:
				write(out, MAP_4);
				break;
			case 5:
				write(out, MAP_5);
				break;
			case 6:
				write(out, MAP_6);
				break;
			case 7:
				write(out, MAP_7);
				break;
			case 8:
				write(out, MAP_8);
				break;
			case 9:
				write(out, MAP_9);
				break;
			case 10:
				write(out, MAP_10);
				break;
			case 11:
				write(out, MAP_11);
				break;
			case 12:
				write(out, MAP_12);
				break;
			case 13:
				write(out, MAP_13);
				break;
			case 14:
				write(out, MAP_14);
				break;
			case 15:
				write(out, MAP_15);
				break;
			case 16:
				write(out, MAP_16);
				break;
			default:
				write(out, MAP);
				writeInt(out, size);
				break;
			}

			Set<Entry<Object, Object>> entrys = map.entrySet();
			for (Entry<Object, Object> e : entrys) {
				Object k = e.getKey();
				Object v = e.getValue();
				writeObject(out, k);
				writeObject(out, v);
			}
		}
	}

	public static void writeMapTag(OutputStream out, int size)
			throws IOException {
		if (size == 0) {
			write(out, MAP_0);
		} else {
			switch (size) {
			case 1:
				write(out, MAP_1);
				break;
			case 2:
				write(out, MAP_2);
				break;
			case 3:
				write(out, MAP_3);
				break;
			case 4:
				write(out, MAP_4);
				break;
			case 5:
				write(out, MAP_5);
				break;
			case 6:
				write(out, MAP_6);
				break;
			case 7:
				write(out, MAP_7);
				break;
			case 8:
				write(out, MAP_8);
				break;
			case 9:
				write(out, MAP_9);
				break;
			case 10:
				write(out, MAP_10);
				break;
			case 11:
				write(out, MAP_11);
				break;
			case 12:
				write(out, MAP_12);
				break;
			case 13:
				write(out, MAP_13);
				break;
			case 14:
				write(out, MAP_14);
				break;
			case 15:
				write(out, MAP_15);
				break;
			case 16:
				write(out, MAP_16);
				break;
			default:
				write(out, MAP);
				writeInt(out, size);
				break;
			}
		}
	}

	@Override
	public void writeIntArray(int[] array) throws IOException {
		writeIntArray(out, array);
	}

	public static void writeIntArray(OutputStream out, int[] array)
			throws IOException {
		if (array == null)
			write(out, NULL);
		else if (array.length == 0)
			write(out, INT_ARRAY_0);
		else {
			int size = array.length;
			write(out, INT_ARRAY);
			writeInt(out, size);
			for (int i : array) {
				writeInt(out, i);
			}
		}

	}

	@Override
	public void writeStrArray(String[] array) throws IOException {
		writeStrArray(out, array);
	}

	public static void writeStrArray(OutputStream out, String[] array)
			throws IOException {
		if (array == null)
			write(out, NULL);
		else if (array.length == 0)
			write(out, STR_ARRAY_0);
		else {
			int size = array.length;
			write(out, STR_ARRAY);
			writeInt(out, size);
			for (String s : array) {
				writeUTF(out, s);
			}
		}

	}

	@Override
	public void writeObject(Object val) throws IOException {
		writeObject(out, val);
	}

	public static void writeObject(OutputStream out, Object val)
			throws IOException {
		if (val == null)
			write(out, NULL);
		// boolean
		else if (val instanceof Boolean) {
			Boolean v = (Boolean) val;
			writeBoolean(out, v);
		} else if (val instanceof AtomicBoolean) {
			AtomicBoolean v = (AtomicBoolean) val;
			writeBoolean(out, v.get());
		} else if (val instanceof Byte) {
			Byte v = (Byte) val;
			writeByte(out, v);
		} else if (val instanceof Short) {
			Short v = (Short) val;
			writeShort(out, v);
		} else if (val instanceof Integer) {
			Integer v = (Integer) val;
			writeInt(out, v);
		} else if (val instanceof AtomicInteger) {
			AtomicInteger v = (AtomicInteger) val;
			writeInt(out, v.get());
		} else if (val instanceof Long) {
			Long v = (Long) val;
			writeLong(out, v);
		} else if (val instanceof AtomicLong) {
			AtomicLong v = (AtomicLong) val;
			writeLong(out, v.get());
		} else if (val instanceof Float) {
			Float v = (Float) val;
			writeFloat(out, v);
		} else if (val instanceof Double) {
			Double v = (Double) val;
			writeDouble(out, v);
		} else if (val instanceof String) {
			String v = (String) val;
			writeUTF(out, v);
		} else if (val instanceof byte[]) {
			byte[] v = (byte[]) val;
			writeBytes(out, v);
		} else if (val instanceof List) {
			List<Object> v = (List<Object>) val;
			writeList(out, v);
		} else if (val instanceof Set) {
			Set<Object> v = (Set<Object>) val;
			writeSet(out, v);
		} else if (val instanceof Map) {
			Map<Object, Object> v = (Map<Object, Object>) val;
			writeMap(out, v);
		} else if (val instanceof Date) {
			Date v = (Date) val;
			writeDate(out, v);
		} else if (val instanceof int[]) {
			int[] v = (int[]) val;
			writeIntArray(out, v);
		} else if (val instanceof String[]) {
			String[] v = (String[]) val;
			writeStrArray(out, v);
		} else {
			throw new NoSuchMethodError("unspport object:" + val);
		}
	}

}
